#include "database_field.h"

#include "database_mysql_wrap.h"

namespace afcore {
namespace database {

CField::CField() {
}

CField::~CField() {
  CleanUp();
}

bool database::CField::GetBool() const {
  return 1 == GetUInt8();
}

uint8_t database::CField::GetUInt8() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int8)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<uint8_t*>(data_.value);
  }
  return static_cast<uint8_t>(strtoul(static_cast<char*>(data_.value), nullptr, 10));
}

int8_t database::CField::GetInt8() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int8)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<int8_t *>(data_.value);
  }
  return static_cast<int8_t>(strtol(static_cast<char*>(data_.value), nullptr, 10));
}

uint16_t database::CField::GetUInt16() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int16)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<uint16_t*>(data_.value);
  }
  return static_cast<uint16_t>(strtoul(static_cast<char*>(data_.value), nullptr, 10));
}

int16_t database::CField::GetInt16() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int16)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<int16_t *>(data_.value);
  }
  return static_cast<int16_t>(strtol(static_cast<char*>(data_.value), nullptr, 10));
}

uint32_t database::CField::GetUInt32() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int32)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<uint32_t*>(data_.value);
  }
  return static_cast<uint32_t>(strtoul(static_cast<char*>(data_.value), nullptr, 10));
}

int32_t database::CField::GetInt32() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int32)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<int32_t*>(data_.value);
  }
  return static_cast<int32_t>(strtol(static_cast<char*>(data_.value), nullptr, 10));
}

uint64_t database::CField::GetUInt64() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int64)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<uint64_t*>(data_.value);
  }
  return static_cast<uint64_t>(strtoull(static_cast<char*>(data_.value), nullptr, 10));
}

int64_t database::CField::GetInt64() const {
  if (!data_.value) {
    return 0;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Int64)) {
    LogWrongType(__FUNCTION__);
    return 0;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<int16_t*>(data_.value);
  }
  return static_cast<int16_t>(strtoll(static_cast<char*>(data_.value), nullptr, 10));
}

float database::CField::GetFloat() const {
  if (!data_.value) {
    return 0.0f;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Float)) {
    LogWrongType(__FUNCTION__);
    return 0.0f;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<float*>(data_.value);
  }
  return static_cast<float>(strtof(static_cast<char*>(data_.value), nullptr));
}

double database::CField::GetDouble() const {
  if (!data_.value) {
    return 0.0f;
  }

#if defined(AFCORE_DEBUG)
  if (!IsType(EDatabaseFieldType::kDatabaseFieldType_Float) &&
    !IsType(EDatabaseFieldType::kDatabaseFieldType_Decimal)) {
    LogWrongType(__FUNCTION__);
    return 0.0f;
  }
#endif

  if (data_.raw) {
    return *reinterpret_cast<double*>(data_.value);
  }
  return static_cast<double>(strtod(static_cast<char*>(data_.value), nullptr));
}

char const *database::CField::GetCString() const {
  if (!data_.value) {
    return nullptr;
  }

#if defined(AFCORE_DEBUG)
  if (IsNumeric() && data_.raw) {
    LogWrongType(__FUNCTION__);
    return nullptr;
  }
#endif

  return static_cast<const char*>(data_.value);
}

std::string database::CField::GetString() const {
  if (!data_.value) {
    return "";
  }

  const char* string = GetCString();
  if (!string) {
    return "";
  }

  return std::string(string, data_.length);
}

std::vector<uint8_t> database::CField::GetBinary() const {
  std::vector<uint8_t> result;

  if (!data_.value || !data_.length) {
    return result;
  }
  result.resize(data_.length);
  memcpy(result.data(), data_.value, data_.length);
  return result;
}

bool database::CField::IsNull() const {
  return nullptr == data_.value;
}

void database::CField::SetByteValue(void *new_value, EDatabaseFieldType new_type, uint32_t length) {
  data_.value = new_value;
  data_.length = length;
  data_.type = new_type;
  data_.raw = true;
}

void database::CField::SetStructuredValue(char *new_value, EDatabaseFieldType new_type, uint32_t length) {
  if (data_.value) {
    CleanUp();
  }

  if (new_value) {
    data_.value = new char[length + 1];
    memcpy(data_.value, new_value, length);
    *(reinterpret_cast<char*>(data_.value) + length) = '\0';
    data_.length = length;
  }

  data_.type = new_type;
  data_.raw = false;
}

void database::CField::CleanUp() {
  if (!data_.raw) {
    delete[] (static_cast<char*>(data_.value));
    data_.value = nullptr;
  }
}

bool database::CField::IsType(EDatabaseFieldType type) const {
  return type == data_.type;
}

bool database::CField::IsNumeric() const {
  return (data_.type == EDatabaseFieldType::kDatabaseFieldType_Int8 ||
    data_.type == EDatabaseFieldType::kDatabaseFieldType_Int16 ||
    data_.type == EDatabaseFieldType::kDatabaseFieldType_Int32 ||
    data_.type == EDatabaseFieldType::kDatabaseFieldType_Int64 ||
    data_.type == EDatabaseFieldType::kDatabaseFieldType_Float ||
    data_.type == EDatabaseFieldType::kDatabaseFieldType_Double);
}

#if defined(AFCORE_DEBUG)

void CField::LogWrongType(const char *getter) const {
  LOG_WARN("{} on {} filed {}.{} ({}.{}) at index {}.",
    getter, meta_.type, meta_.table_alias, meta_.alias, meta_.table_name, meta_.name, meta_.index);
}

#include "database_mysql_wrap.h"

static const char* FieldTypeToString(enum_field_types type) {
  switch(type) {
    case MYSQL_TYPE_BIT:         return "BIT";
    case MYSQL_TYPE_BLOB:        return "BLOB";
    case MYSQL_TYPE_DATE:        return "DATE";
    case MYSQL_TYPE_DATETIME:    return "DATETIME";
    case MYSQL_TYPE_NEWDECIMAL:  return "NEWDECIMAL";
    case MYSQL_TYPE_DECIMAL:     return "DECIMAL";
    case MYSQL_TYPE_DOUBLE:      return "DOUBLE";
    case MYSQL_TYPE_ENUM:        return "ENUM";
    case MYSQL_TYPE_FLOAT:       return "FLOAT";
    case MYSQL_TYPE_GEOMETRY:    return "GEOMETRY";
    case MYSQL_TYPE_INT24:       return "INT24";
    case MYSQL_TYPE_LONG:        return "LONG";
    case MYSQL_TYPE_LONGLONG:    return "LONGLONG";
    case MYSQL_TYPE_LONG_BLOB:   return "LONG_BLOB";
    case MYSQL_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB";
    case MYSQL_TYPE_NEWDATE:     return "NEWDATE";
    case MYSQL_TYPE_NULL:        return "NULL";
    case MYSQL_TYPE_SET:         return "SET";
    case MYSQL_TYPE_SHORT:       return "SHORT";
    case MYSQL_TYPE_STRING:      return "STRING";
    case MYSQL_TYPE_TIME:        return "TIME";
    case MYSQL_TYPE_TIMESTAMP:   return "TIMESTAMP";
    case MYSQL_TYPE_TINY:        return "TINY";
    case MYSQL_TYPE_TINY_BLOB:   return "TINY_BLOB";
    case MYSQL_TYPE_VAR_STRING:  return "VAR_STRING";
    case MYSQL_TYPE_YEAR:        return "YEAR";
    default:                     return "-Unknown-";
  }
}

void CField::SetMetadata(SMysqlField *field, uint32_t field_index) {
  meta_.table_name = field->org_table;
  meta_.table_alias = field->table;
  meta_.name = field->org_name;
  meta_.alias = field->name;
  meta_.type = FieldTypeToString(field->type);
  meta_.index = field_index;
}

#endif

} // !namespace database
} // !namespace afcore
